;; 定义
;; ==================================================
        ;; 数码管用
        TUBE_INIT       EQU         P0
        TUBE_SRCK       BIT         P0.4
        TUBE_RCK        BIT         P0.5
        TUBE_RESET      BIT         P0.6
        TUBE_DATA       BIT         P0.7
        ;; 计时器模式,T0T1都处于模式1
        MODE            EQU         11H
        ;; 定时器每溢出20次为1s
        COUNT_TIMES     EQU         20 
        ;; 2EH-2FH用于记录某些状态
        CHOSE_TIMER     EQU         2FH.7
        CHOSE_COUNTDOWN EQU         2FH.6
        ;; 蜂鸣器模式修改为倒计时时每减少一秒，响一次
        OPEN_BUZZER     EQU         2FH.4
        LEFT_KEY        EQU         2FH.3
        RIGHT_KEY       EQU         2FH.2
        DOWN_KEY        EQU         2FH.1
        UP_KEY          EQU         2FH.0
        INNER_RESET     EQU         2EH.7
        STOP_KEY        EQU         2EH.6
        IF_START        EQU         2EH.5
        KEY_MINH        EQU         2EH.3
        KEY_MINL        EQU         2EH.2
        KEY_SECH        EQU         2EH.1
        KEY_SECL        EQU         2EH.0

        ;; 用于存放显示器内容的位置
        MIN             EQU         21H
        SEC             EQU         20H

;; ==================================================
;; 中断向量设置
;; ==================================================
        ORG	    0
	AJMP	MAIN
        ;; 设置中断向量INT0
	ORG 	03H		 
	AJMP	RESET
        ;; 设置定时器TIMER0中断
	ORG	0BH		 
	AJMP	TIMER0
        ;; 设置中断向量INT1
	ORG	    13H		 
	AJMP	START_OR_PAUSE
        ;; 设置定时器TIMER1中断
	ORG	    1BH		 
	AJMP	TIMER1

;; ==================================================
;; main body
;; ==================================================
        
MAIN:
        MOV     SP, #60H
        ;; 系统复位,显示0000
        MOV     MIN, #0
        MOV     SEC, #0
        ;; 显示器规范
        MOV     R0, SEC
        MOV     R1, MIN
        SETB 	EA		
    	SETB	EX0	
        SETB    EX1	 		
	SETB	ET0		 
	SETB	ET1
        SETB    PX0
        
        CLR     TR0
        CLR     TR1
        CLR     IF_START
        ;; 初始时关闭蜂鸣器
        SETB    P1.3
        CLR     OPEN_BUZZER
        ;; 设置IT0、IT1为低电平工作
        CLR     IT0
        CLR     IT1	
        ;; 设置倒计时器工作状态
        MOV     TMOD, #MODE
        MOV     TH0, #3CH
        MOV     TL0, #0B0H
        MOV     TH1, #3CH
        MOV     TL1, #0B0H
        
MAIN_LOOP:
        ;; 键盘扫描
        ACALL   KEYBOARD_SCAN

        ;; 计时器
        ACALL   TIMER

        ;; 倒计时
        ACALL   COUNT_DOWN

        ;; 数码管显示
        ACALL   DISPLAY

        AJMP    MAIN_LOOP
;; ==================================================
;; 键盘扫描
;; ==================================================
KEYBOARD_SCAN:
        ;; 键盘扫描            || 总复位 || 开/停  ||
        ;; ||====================================||
        ;; || 计时器 || 倒计时 ||  蜂鸣  ||       ||
        ;; ||====================================||
        ;; ||  复位  ||停止计时||       ||        ||
        ;; ||====================================||
        ;; ||  LEFT  || RIGHT ||   DOWN ||  UP   ||
        ;; ||====================================||
        ;; ||   1    ||  2    ||    3   ||   4   ||
        ;; ||====================================||

        ;; 输入：键盘
        ;; 输出：位地址 74H ~ 7FH
        MOV     R4, #0FFH
	MOV     P2, #0F0H
	MOV     A, P2
	CJNE    A, #0F0H, KK
	;; 查询是否有键按下
	AJMP	KEYBOARD_EXIT
KK:
	ACALL   DLY
	MOV	A, P2
	CJNE	A, #0F0H, KK1
	AJMP	KEYBOARD_EXIT
	
KK1:
        MOV	R4, P2
        MOV     P2, #0FH
        MOV     A, P2
        ORL     A, R4

        ;; 扫描键盘确认按下的位置
KEY11:
	CJNE 	A, #01110111B, KEY12
	SETB	CHOSE_TIMER
	AJMP	KEYBOARD_EXIT
	;; 跳转至计时器
KEY12:
	CJNE	A, #01111011B, KEY13
	SETB	CHOSE_COUNTDOWN
        AJMP	KEYBOARD_EXIT
	;; 倒计时器功能选择
KEY13:
	CJNE	A, #01111101B, KEY14
        CPL    OPEN_BUZZER
	AJMP	KEYBOARD_EXIT
	;; 蜂鸣器模式开关
KEY14:
	CJNE	A, #01111110B, KEY21
	AJMP	KEYBOARD_EXIT
	;; 闲置键
KEY21:
	CJNE	A, #10110111B, KEY22
	SETB	INNER_RESET
	AJMP	KEYBOARD_EXIT
	;; 内部复位
KEY22:
	CJNE	A, #10111011B, KEY23
	SETB	STOP_KEY
	AJMP	KEYBOARD_EXIT
	;; 停止计时
KEY23:
	CJNE	A, #10111101B, KEY24
	AJMP	KEYBOARD_EXIT
	;; 闲置键
KEY24:
        CJNE	A, #10111110B, KEY31
	AJMP	KEYBOARD_EXIT
	;; 闲置键
KEY31:
	CJNE	A, #11010111B, KEY32
	SETB	LEFT_KEY
        CLR     RIGHT_KEY
	AJMP	KEYBOARD_EXIT
	;; 多功能左键
KEY32:
	CJNE	A, #11011011B, KEY33
	SETB	RIGHT_KEY
        CLR     LEFT_KEY
	AJMP	KEYBOARD_EXIT
	;; 多功能右键
KEY33:
	CJNE	A, #11011101B, KEY34
	SETB	DOWN_KEY
        CLR     UP_KEY
	AJMP	KEYBOARD_EXIT
	;; 时间设置DOWN键
KEY34:
    	CJNE	A, #11011110B, KEY41
	SETB	UP_KEY
        CLR     DOWN_KEY
	AJMP	KEYBOARD_EXIT
	;; 时间设置UP键
KEY41:
	CJNE	A, #11100111B, KEY42
        SETB    KEY_MINH
        CLR     KEY_MINL
        CLR     KEY_SECH
        CLR     KEY_SECL
	AJMP 	KEYBOARD_EXIT
	;; MINH键
KEY42:
	CJNE	A, #11101011B, KEY43
        SETB    KEY_MINL
        CLR     KEY_MINH
        CLR     KEY_SECH
        CLR     KEY_SECL
	AJMP 	KEYBOARD_EXIT
	;; MINL键
KEY43:
	CJNE	A, #11101101B, KEY44
        SETB    KEY_SECH
        CLR     KEY_MINH
        CLR     KEY_MINL
        CLR     KEY_SECL
	AJMP 	KEYBOARD_EXIT
	;; SECH键
KEY44:
        CJNE    A, #11101110B, KEYBOARD_EXIT
        SETB    KEY_SECL
        CLR     KEY_MINH
        CLR     KEY_MINL
        CLR     KEY_SECH
	AJMP 	KEYBOARD_EXIT
	;; SECL键

DLY: 
 	MOV 	R7, #256 		   
DL1:	
	MOV 	R6, #256
	DJNZ 	R6, $    	
	DJNZ 	R7, DL1
    	RET
KEYBOARD_EXIT:
        RET

;; ==================================================
;; 系统复位
;; ==================================================
RESET:
        ;; INT0
        ;; 关闭中断INT0,防止多次中断
        CLR	EX0
        ACALL   DLY		
        ;; 计时暂停	 
	CLR	TR0
        CLR     TR1
        SETB    P1.3			 
	JB	IE0, RESET_EXIT
        MOV     MIN, #00H
        MOV     SEC, #00H
	MOV     R0, SEC
        MOV     R1, MIN
        CLR     CHOSE_COUNTDOWN
        CLR     CHOSE_TIMER
        CLR     IF_START
        CLR     KEY_MINH
        CLR     KEY_MINL
        CLR     KEY_SECH
        CLR     KEY_SECL
        CLR     OPEN_BUZZER
        CLR     STOP_KEY

RESET_EXIT:
        SETB    EX0
        RETI

;; ==================================================
;; 计时开始/暂停
;; ==================================================
START_OR_PAUSE:
    	CLR	EX1
        ACALL  	DLY
	JB  	IE1,SP_EXIT
        JB      CHOSE_TIMER, TIMER_BEGIN
        JB      CHOSE_COUNTDOWN, COUNTDOWN_BEGIN
TIMER_BEGIN:
        CPL     TR0
        AJMP    SP_EXIT
COUNTDOWN_BEGIN:
        CPL     TR1
SP_EXIT:
        SETB	EX1
	RETI

;; ==================================================
;; 计时开始/暂停
;; ==================================================
TIMER:
        JB      CHOSE_TIMER, TIMER_WORK
	RET

TIMER_WORK:
        JB     IF_START, START
        CLR     CHOSE_COUNTDOWN
	;; 初始设置为 0
        MOV     MIN, #00H
        MOV     SEC, #00H
        SETB    IF_START

        ;; 计时用
        MOV     R5, #COUNT_TIMES
        AJMP    TIMER_EXIT

START:
	JB      INNER_RESET, INNER_RESET_T
        AJMP    TIMER_EXIT

INNER_RESET_T:
        CLR     INNER_RESET
        CLR     IF_START
        AJMP    TIMER_WORK
        AJMP    TIMER_EXIT

TIMER_EXIT:
        MOV     R0, SEC
        MOV     R1, MIN
        RET

;; ==================================================
;; 计时(T0中断控制)
;; ==================================================
;; R5记录溢出次数，每10次为1s，做出对应响应
TIMER0:	
        DJNZ	R5, TIMER0_AGAIN
        ACALL   INCREASE
	MOV 	R5, #COUNT_TIMES
TIMER0_AGAIN:
        MOV	TH0, #3CH
	MOV	TL0, #0B0H
	CLR 	TF0
	RETI

INCREASE:
	MOV	A, SEC
	;;取秒钟低位
	ANL	A, #0FH
	;;查看低位是否为九
	XRL	A, #09H
	;;不是的话正常加1
	JNZ	INC_SECL
	;;是的话让他变为0
	MOV	A, #0F0H
	ANL	SEC, A

	;;
	MOV	A, SEC
	ANL	A, #0F0H
	XRL	A, #50H
	JNZ	INC_SECH
	MOV	A, #0FH
	ANL	SEC,A

	;;
	MOV	A, MIN
	ANL	A, #0FH
	XRL	A, #09H
	JNZ	INC_MINL
	MOV	A, #0F0H
	ANL	MIN, A

	;;
	MOV	A, MIN
	ANL	A, #0F0H
	XRL	A, #90H
	JNZ	INC_MINH
	
	;;已经计时到 99:59
	MOV	MIN, #99H
	MOV	SEC, #59H
	AJMP	INC_EXIT

INC_SECL:
	INC	SEC
        AJMP    INC_EXIT
INC_SECH:
        MOV     A, SEC
        ADD     A, #10H
        MOV     SEC, A
        AJMP    INC_EXIT
INC_MINL:
        INC     MIN
        AJMP    INC_EXIT
INC_MINH:
        MOV     A, MIN
        ADD     A, #10H
        MOV     MIN, A

INC_EXIT:
        RET 


;; ==================================================
;; 倒计时(设置时间)
;; ==================================================

COUNT_DOWN:
        ;; 先判断是否为倒计时模式

        ;; initial 1 min
        ;; set time （初始化位置在第二位）
        ;; start/pause        
        ;; UP
        ;; DOWN
        ;; left, right

        ;; 等待7E=1，倒计时器功能被选中
        JB     CHOSE_COUNTDOWN, COUNT_WORK
        RET

COUNT_WORK:
        
        ;; 设置后每次溢出需50ms，循环10次为1s

        JB      IF_START, CON1
        ;; 初始设置为1分钟
        MOV     MIN, #01H
        MOV     SEC, #00H

        ;; 从左到右MINH为第一位，
        SETB    KEY_MINL
        SETB    IF_START
        SETB    P1.3
        CLR     TR1
        CLR     CHOSE_TIMER

        ;; 倒计时用
        MOV     R5, #COUNT_TIMES
        AJMP    SETTIME_EXIT
        
CON1:

        
        JB      INNER_RESET, INNER_RESET_C
        JB      KEY_MINH, SETTIME
        JB      KEY_MINL, SETTIME
        JB      KEY_SECH, SETTIME
        JB      KEY_SECL, SETTIME
        
        AJMP    SETTIME_EXIT
INNER_RESET_C:
        CLR     INNER_RESET
        CLR     IF_START
        SETB    P1.3
        AJMP    COUNT_WORK
        AJMP    SETTIME_EXIT



SETTIME:
        JB      DOWN_KEY, DOWN
        JB      UP_KEY, UP      
        AJMP    SETTIME_EXIT

DOWN:
        CLR     DOWN_KEY
        JB      KEY_MINH, MINH_D
        JB      KEY_SECH, SECH_D
        JB      KEY_MINL, MINL_D
        JB      KEY_SECL, SECL_D
        AJMP    SETTIME_EXIT

MINH_D:
        ;; 高位减1
        ;; 当高位已经为0时，按DOWN无反应
        MOV     A, MIN
        ANL     A, #0F0H
        CJNE    A, #0, MINH_DOWN
        AJMP    SETTIME_EXIT
MINH_DOWN:
        CLR     C
        MOV     A, MIN
        SUBB    A, #10H
        MOV     MIN, A
        AJMP    SETTIME_EXIT


MINL_D:
        ;; 当已经为0时，按DOWN无反应
        MOV     A, MIN
        ANL     A, #0FH
        CJNE    A, #0, MINL_DOWN
        AJMP    SETTIME_EXIT
MINL_DOWN:
        CLR     C
        MOV     A, MIN
        SUBB    A, #01H
        MOV     MIN, A
        AJMP    SETTIME_EXIT

SECH_D:
        ;; 当已经为0时，按DOWN无反应
        MOV     A, SEC
        ANL     A, #0F0H
        CJNE    A, #0, SECH_DOWN
        AJMP    SETTIME_EXIT
SECH_DOWN:
        CLR     C
        MOV     A, SEC
        SUBB    A, #10H
        MOV     SEC, A
        AJMP    SETTIME_EXIT

SECL_D:
        ;; 当已经为0时，按DOWN无反应
        MOV     A, SEC
        ANL     A, #0FH
        CJNE    A, #0, SECL_DOWN
        AJMP    SETTIME_EXIT
SECL_DOWN:
        CLR     C
        MOV     A, SEC
        SUBB    A, #01H
        MOV     SEC, A
        AJMP    SETTIME_EXIT



UP:
        CLR     UP_KEY
        JB      KEY_MINH, MINH_U
        JB      KEY_SECH, SECH_U
        JB      KEY_MINL, MINL_U
        JB      KEY_SECL, SECL_U
        AJMP    SETTIME_EXIT

MINH_U:
        ;; 当高位已经为5时，按UP无反应
        MOV     A, MIN
        ANL     A, #0F0H
        CJNE    A, #50H, MINH_UP
        AJMP    SETTIME_EXIT
MINH_UP:
        MOV     A, MIN
        ADD     A, #10H
        MOV     MIN, A
        AJMP    SETTIME_EXIT


MINL_U:
        ;; 当低位已经为9时，按UP无反应
        MOV     A, MIN
        ANL     A, #0FH
        CJNE    A, #9, MINL_UP
        AJMP    SETTIME_EXIT
MINL_UP:
        MOV     A, MIN
        ADD     A, #01H
        MOV     MIN, A
        AJMP    SETTIME_EXIT

SECH_U:
        ;; 当高位已经为5时，按UP无反应
        MOV     A, SEC
        ANL     A, #0F0H
        CJNE    A, #50H, SECH_UP
        AJMP    SETTIME_EXIT
SECH_UP:
        MOV     A, SEC
        ADD     A, #10H
        MOV     SEC, A
        AJMP    SETTIME_EXIT

SECL_U:
        ;; 当低位已经为9时，按UP无反应
        MOV     A, SEC
        ANL     A, #0FH
        CJNE    A, #9, SECL_UP
        AJMP    SETTIME_EXIT
SECL_UP:
        MOV     A, SEC
        ADD     A, #01H
        MOV     SEC, A
        AJMP    SETTIME_EXIT



SETTIME_EXIT:
        MOV     R0, SEC
        MOV     R1, MIN
        RET

;; ==================================================
;; 倒计时(T1中断控制)
;; ==================================================
        ;; R5记录溢出次数，每10次为1s，做出对应响应
TIMER1:	
        DJNZ	R5, TIMER1_AGAIN
        ACALL   DECREASE
	MOV 	R5, #COUNT_TIMES
TIMER1_AGAIN:
        MOV	TH1, #3CH
	MOV	TL1, #0B0H
	CLR 	TF1
	RETI

;; 实现秒数减1
DECREASE:
        ;; 倒计时操作过程中要检查是否有开启蜂鸣器模式

        MOV     A, SEC
        ANL     A, #0FH
	JNZ     DEC_SECL
	ORL     SEC, #09H

        ;; 如果SECL为0，则需要借位 
	MOV     A, SEC
        ANL     A, #0F0H
	JNZ	DEC_SECH
	ORL     SEC, #50H

	;; 如果SECH为0，则需要借位
	MOV     A, MIN
        ANL     A, #0FH
	JNZ	DEC_MINL
	ORL     MIN, #09H

	;; 如果MINL为0，则需要借位
	MOV     A, MIN
        ANL     A, #0F0H
	JNZ	DEC_MINH

	;; 此时倒计时完成，保持00：00，蜂鸣器发声
	MOV 	MIN, #0
	MOV	SEC, #0
        ;; 这时需要启动内部复位键蜂鸣器才会关掉
	CLR	P1.3	 			
        RET
DEC_SECL:	
        DEC     SEC
        AJMP    DEC_EXIT
DEC_SECH:
        CLR     C
        MOV     A, SEC
        SUBB    A, #10H
        MOV     SEC, A
        AJMP    DEC_EXIT
DEC_MINL:
        DEC     MIN
        AJMP    DEC_EXIT
DEC_MINH:
        CLR     C
        MOV     A, MIN
        SUBB    A, #10H
        MOV     MIN, A

DEC_EXIT:
        JB      OPEN_BUZZER, BEE
        RET 
BEE:    
        CLR     P1.3
        ACALL   DLY
        SETB    P1.3
        RET



        
;; ==================================================
;; 数码管显示
;; ==================================================
DISPLAY:
        ;; 查七段数码管表
        ;; 0 -> abcdef  -> fc
        ;; 1 -> bc      -> 60
        ;; 2 -> abdeg   -> da
        ;; 3 -> abcdg   -> f2
        ;; 4 -> bcfg    -> 66
        ;; 5 -> acdfg   -> b6
        ;; 6 -> acdefg  -> be
        ;; 7 -> abc     -> e0
        ;; 8 -> abcdefg -> fe
        ;; 9 -> abcdfg  -> f6
        ;; . -> h       -> 00000001B

        MOV     DPTR, #TUBE_TABLE
        SETB    TUBE_RESET

TUBE_MIN_DECADE:
        MOV     TUBE_INIT, #01H 
        MOV     A, R1
        SWAP    A
        ANL     A, #0FH
        MOVC    A, @A+DPTR
		MOV     R3, A
        ACALL   SHOW_NUM
        ACALL   TUBE_DELAY

TUBE_MIN_UNIT:
        MOV     TUBE_INIT, #02H
        MOV     A, R1
        ANL     A, #0FH
        MOVC    A, @A+DPTR
		ADD     A, #01H
        MOV     R3, A
        ACALL   SHOW_NUM
        ACALL   TUBE_DELAY       

TUBE_S_DECADE:
        MOV     TUBE_INIT, #04H
        MOV     A, R0
        SWAP    A
        ANL     A, #0FH
        MOVC    A, @A+DPTR
        MOV     R3, A
        ACALL   SHOW_NUM
        ACALL   TUBE_DELAY

TUBE_S_UNIT:
        MOV     TUBE_INIT, #08H
        MOV     A, R0
        ANL     A, #0FH
        MOVC    A, @A+DPTR
        MOV     R3, A
        ACALL   SHOW_NUM
        ACALL   TUBE_DELAY

        RET

SHOW_NUM:
        MOV     31H, #08H
        MOV     A, R3
MOVE_DATE:
        ANL     A, #01H
        JNZ     SET_DATA
CLR_DATA:
        CLR     TUBE_DATA
        AJMP    SKIP_SET
SET_DATA:
        SETB    TUBE_DATA
SKIP_SET:
        SETB    TUBE_SRCK
        CLR     TUBE_SRCK
        MOV     A, R3
        RR      A
        MOV     R3, A
        DJNZ    31H, MOVE_DATE
        SETB    TUBE_RCK
        CLR     TUBE_RCK
        RET

;; ==================================================
;; 数码管延时
;; ==================================================

TUBE_DELAY:
        MOV     R7, #150
DELAY:
        MOV     R6, #20
        DJNZ    R6, $
        DJNZ    R7, DELAY
        RET


;; ==================================================
;; 数码管段码表
;; ==================================================

TUBE_TABLE:     DB      0FCH, 60H, 0DAH, 0F2H, 066H, 0B6H, 0BEH, 0E0H, 0FEH, 0F6H
        END